#include <Render/Include/RendererDX9.h>

#include <Render/Include/RenderObjectDX9.h>

#include <D3dx9shape.h>

RendererDX9::RendererDX9(IWinWindow* window)
    :Renderer()
{
    LPDIRECT3D9 p_d3d9;
    D3DDISPLAYMODE d3ddm;
    
    // Create the D3D object.
    if (NULL == (p_d3d9 = Direct3DCreate9(D3D_SDK_VERSION)))
    {
        throw -1;
    }

    // Get the current desktop display mode, so we can set up a back
    // buffer of the same format
    if (FAILED(p_d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    {
        throw -1;
    }

    D3DPRESENT_PARAMETERS d3dpp; 
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    // We're using the current display mode. 
    d3dpp.BackBufferFormat = d3ddm.Format;
    d3dpp.BackBufferWidth = window->getWidth();
    d3dpp.BackBufferHeight = window->getHeight();

    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D32;
    HRESULT hr = p_d3d9->CheckDepthStencilMatch(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3ddm.Format, d3ddm.Format, D3DFMT_D32);

    if (FAILED (hr))
    {
        d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    }

    // Create the D3DDevice
    if (FAILED(p_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window->getHWND(), D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &(this->p_device))))
    {
        throw -1;
    }

    D3DVIEWPORT9 vp;
    vp.Width = window->getWidth();
    vp.Height = window->getHeight();
    vp.MinZ = 0.0f;
    vp.MaxZ = 1.0f;
    vp.X = 0;
    vp.Y = 0;
    this->p_device->SetViewport(&vp);

    // Turn on the zbuffer
    this->p_device->SetRenderState(D3DRS_ZENABLE, TRUE);

    this->createBallRenderObject();
    this->createCubeRenderObject();

    // to render text
    // Create the D3DX Font
    hr = D3DXCreateFont( this->p_device, 20, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
        L"Arial", &this->font ) ;

}

RendererDX9::~RendererDX9(void)
{
    delete this->ball_render_object;
    delete this->cube_render_object;
    this->font->Release();
    this->p_device->Release();
}

void RendererDX9::renderScene(Scene* scene, Paddle* local_player_paddle)
{
    HRESULT hr;
    
    hr = this->p_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, (D3DCOLOR)0x00000000, 1.0f, 0x00000000);
    if (D3D_OK == this->p_device->BeginScene()) 
    {
        this->configViewPoint(scene, local_player_paddle);

        this->renderSceneWalls(scene);

        std::vector<Ball*> balls = scene->getBalls();
        for (unsigned int i = 0; i < balls.size(); i++)
        {
            this->renderBall( balls[i] );
        }

        std::vector<Block*> blocks = scene->getBlocks();
        for (unsigned int i = 0; i < blocks.size(); i++)
        {
            this->renderBlock( blocks[i] );
        }

        std::vector<Paddle*> paddles = scene->getPaddles();
        for (unsigned int i = 0; i < paddles.size(); i++)
        {
            this->renderPaddle( paddles[i] );
        }

        this->p_device->EndScene();
    }
    this->drawText();
    this->p_device->Present(NULL, NULL, NULL, NULL);
    this->clearText();
}

void RendererDX9::renderBlack()
{
    HRESULT hr;

    hr = this->p_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, (D3DCOLOR)0x00000000, 1.0f, 0x00000000);
    this->drawText();
    this->p_device->Present(NULL, NULL, NULL, NULL);
    this->clearText();
}

void RendererDX9::createBallRenderObject()
{
    HRESULT hr;
    LPD3DXMESH mesh_ball;
    LPD3DXBUFFER adjacency_mesh_ball;
    hr = D3DXCreateSphere( this->p_device, 1.0, 20, 20, &mesh_ball, &adjacency_mesh_ball);
    this->ball_render_object = new RenderObjectDX9(mesh_ball, adjacency_mesh_ball);        
}

void RendererDX9::createCubeRenderObject()
{
    HRESULT hr;
    LPD3DXMESH mesh_ball;
    LPD3DXBUFFER adjacency_mesh_ball;
    hr = D3DXCreateBox( this->p_device, 1.0, 1.0, 1.0, &mesh_ball, &adjacency_mesh_ball);
    this->cube_render_object = new RenderObjectDX9(mesh_ball, adjacency_mesh_ball);        
}

void RendererDX9::configViewPoint(Scene* scene, Paddle* local_player_paddle)
{
    /// set the viewpoint
    D3DXMATRIX view_matrix;
    D3DXVECTOR3 camera_position;
    D3DXVECTOR3 camera_at;
    D3DXVECTOR3 camera_up;
    if (this->render_3D)
    {
        camera_position = D3DXVECTOR3(-25.0f, local_player_paddle->getPositionY(), 35.0f);
        camera_at = D3DXVECTOR3((scene->getMaxX() - scene->getMinX())/2.0f + scene->getMinX(),
            (scene->getMaxY() - scene->getMinY())/2.0f + scene->getMinY(),
            0.0f);
        camera_up = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    }
    else
    {
        camera_position = D3DXVECTOR3((scene->getMaxX() - scene->getMinX())/2.0f + scene->getMinX(),
                                       (scene->getMaxY() - scene->getMinY())/2.0f + scene->getMinY(),
                                       20.0f); 
        camera_at = D3DXVECTOR3((scene->getMaxX() - scene->getMinX())/2.0f + scene->getMinX(),
                                (scene->getMaxY() - scene->getMinY())/2.0f + scene->getMinY(),
                                0.0f); 
        camera_up = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    }
    
    D3DXMatrixLookAtLH(&view_matrix, &camera_position, &camera_at, &camera_up);
    p_device->SetTransform(D3DTS_VIEW, &view_matrix);

    /// set the projection
    D3DXMATRIX projection_matrix;
    if (this->render_3D)
    {
        D3DXMatrixPerspectiveFovLH(&projection_matrix, 3.14f/3.0f, 1.0f, 1.0f, 1000.0f);
    }
    else
    {
        D3DXMatrixOrthoLH(&projection_matrix, scene->getMaxX() - scene->getMinX(), scene->getMaxY() - scene->getMinY(), 1.0f, 1000.0f);
    }
    
    p_device->SetTransform(D3DTS_PROJECTION, &projection_matrix);
}

void RendererDX9::renderSceneWalls(Scene* scene)
{
    p_device->SetRenderState(D3DRS_FILLMODE , D3DFILLMODE::D3DFILL_SOLID);

    p_device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

    p_device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);

    D3DMATERIAL9 d3d9material;
    ZeroMemory(&d3d9material, sizeof(D3DMATERIAL9));
    d3d9material.Emissive.r = 1.0f;
    d3d9material.Emissive.g = 1.0f;
    d3d9material.Emissive.b = 1.0f;
    d3d9material.Emissive.a = 1.0f;

    p_device->SetMaterial(&d3d9material);

    p_device->SetTexture (0, NULL);

    LPD3DXMESH d3dxmesh = reinterpret_cast<RenderObjectDX9*>(this->cube_render_object)->getLPD3DXMESH();
    
    /// set the transformation matrix for the walls
    D3DXMATRIX scale_matrix;
    //vertical wall
    D3DXMatrixScaling(&scale_matrix, scene->getMaxX() - scene->getMinX(), 0.5f, 0.5f);
    D3DXMATRIX position_matrix;
    D3DXMatrixTranslation(&position_matrix, (scene->getMaxX() - scene->getMinX()) / 2.0f + scene->getMinX(), scene->getMinY(), 0.0f);
    D3DXMATRIX world_matrix;
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);
    if (d3dxmesh != NULL)
    {
        HRESULT hr = d3dxmesh->DrawSubset(0);
    }

    D3DXMatrixTranslation(&position_matrix, (scene->getMaxX() - scene->getMinX()) / 2.0f + scene->getMinX(), scene->getMaxY(), 0.0f);
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);
    d3dxmesh->DrawSubset(0);

    D3DXMatrixScaling(&scale_matrix, 0.5f, scene->getMaxY() - scene->getMinY(), 0.5f);
    D3DXMatrixTranslation(&position_matrix, scene->getMaxX(),(scene->getMaxY() - scene->getMinY()) / 2.0f + scene->getMinY(), 0.0f);
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);
    d3dxmesh->DrawSubset(0);

}

void RendererDX9::renderPaddle(Paddle* paddle)
{
    /// set the transformation matrix for the paddle
    D3DXMATRIX scale_matrix;
    D3DXMatrixScaling(&scale_matrix, paddle->getHeight(), paddle->getWidth(), 1.0);
        
    D3DXMATRIX position_matrix;
    D3DXMatrixTranslation(&position_matrix, paddle->getPositionX(), paddle->getPositionY(), 0.0);

    D3DXMATRIX world_matrix;
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);

    p_device->SetRenderState(D3DRS_FILLMODE , D3DFILLMODE::D3DFILL_SOLID);

    p_device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

    p_device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);

    D3DMATERIAL9 d3d9material;
    ZeroMemory(&d3d9material, sizeof(D3DMATERIAL9));
    d3d9material.Emissive.r = 1.0f;
    d3d9material.Emissive.g = 0.0f;
    d3d9material.Emissive.b = 0.0f;
    d3d9material.Emissive.a = 1.0f;

    p_device->SetMaterial(&d3d9material);

    p_device->SetTexture (0, NULL);

    LPD3DXMESH d3dxmesh = reinterpret_cast<RenderObjectDX9*>(this->cube_render_object)->getLPD3DXMESH();
    if (d3dxmesh != NULL)
    {
        HRESULT hr = d3dxmesh->DrawSubset(0);
    }
}

void RendererDX9::renderBall(Ball* ball)
{
    /// set the transformation matrix for the paddle
    D3DXMATRIX scale_matrix;
    D3DXMatrixScaling(&scale_matrix, ball->getRadio(), ball->getRadio(), ball->getRadio());

    D3DXMATRIX position_matrix;
    D3DXMatrixTranslation(&position_matrix, ball->getPositionX(), ball->getPositionY(), 0.0);

    D3DXMATRIX world_matrix;
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);

    p_device->SetRenderState(D3DRS_FILLMODE , D3DFILLMODE::D3DFILL_SOLID);

    p_device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

    p_device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);

    D3DMATERIAL9 d3d9material;
    ZeroMemory(&d3d9material, sizeof(D3DMATERIAL9));
    d3d9material.Emissive.r = 0.0f;
    d3d9material.Emissive.g = 1.0f;
    d3d9material.Emissive.b = 0.0f;
    d3d9material.Emissive.a = 1.0f;

    p_device->SetMaterial(&d3d9material);

    p_device->SetTexture (0, NULL);

    LPD3DXMESH d3dxmesh = reinterpret_cast<RenderObjectDX9*>(this->ball_render_object)->getLPD3DXMESH();
    if (d3dxmesh != NULL)
    {
        HRESULT hr = d3dxmesh->DrawSubset(0);
    }
}

void RendererDX9::renderBlock(Block* block)
{
    /// set the transformation matrix for the block
    D3DXMATRIX scale_matrix;
    D3DXMatrixScaling(&scale_matrix, block->getHeight(), block->getWidth(), 1.0);

    D3DXMATRIX position_matrix;
    D3DXMatrixTranslation(&position_matrix, block->getPositionX(), block->getPositionY(), 0.0);

    D3DXMATRIX world_matrix;
    D3DXMatrixMultiply(&world_matrix, &scale_matrix, &position_matrix);
    p_device->SetTransform(D3DTS_WORLDMATRIX(0), &world_matrix);

    p_device->SetRenderState(D3DRS_FILLMODE , D3DFILLMODE::D3DFILL_SOLID);

    p_device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

    p_device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);

    D3DMATERIAL9 d3d9material;
    ZeroMemory(&d3d9material, sizeof(D3DMATERIAL9));
    d3d9material.Emissive.r = 0.0f;
    d3d9material.Emissive.g = 0.0f;
    d3d9material.Emissive.b = 1.0f;
    d3d9material.Emissive.a = 1.0f;

    p_device->SetMaterial(&d3d9material);

    p_device->SetTexture (0, NULL);

    LPD3DXMESH d3dxmesh = reinterpret_cast<RenderObjectDX9*>(this->cube_render_object)->getLPD3DXMESH();
    if (d3dxmesh != NULL)
    {
        HRESULT hr = d3dxmesh->DrawSubset(0);
    }
}

void RendererDX9::drawText()
{
    HRESULT hr;
    RECT rc;    
    for (unsigned int i = 0; i < this->text_to_render.size(); i++)
    {
        SetRect(&rc, this->text_to_render[i]->pos_x, this->text_to_render[i]->pos_y, 200, 200 );
        hr = font->DrawTextA(NULL, this->text_to_render[i]->text.c_str(), -1, &rc, DT_NOCLIP, D3DCOLOR_RGBA(255, 255, 255, 255));         
    }
}
